﻿using System.IO;
using System.Net.Sockets;
using System.Text.Json;
using K9Nano.Updater.Utils;
using Microsoft.Extensions.Options;

namespace K9Nano.Updater;

public class DefaultUpdateClient : IUpdateClient
{
    private readonly UpdaterOptions _options;
    private readonly HttpClient _httpClient;

    public DefaultUpdateClient(IOptions<UpdaterOptions> optionsAccessor)
    {
        _options = optionsAccessor.Value;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(_options.BaseUrl),
        };

        _httpClient.DefaultRequestHeaders.Add(Constants.HeaderClientId, _options.ClientId);
    }

    private async Task<HttpResponseMessage> GetAsync(string uri, HttpCompletionOption httpOption = HttpCompletionOption.ResponseContentRead, CancellationToken cancellation = default)
    {
        var requstUri = _options.RequestPath + uri;
        var timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var text = string.Format(Constants.SignTemplate, _options.ClientId, _options.ClientSecret, timeStamp, requstUri);
        var sign = HashUtils.Md5(text);

        var requestMessage = new HttpRequestMessage(HttpMethod.Get, requstUri);
        requestMessage.Headers.Add(Constants.HeaderSign, sign);
        requestMessage.Headers.Add(Constants.HeaderTimeStamp, timeStamp);

        var response = await _httpClient.SendAsync(requestMessage, httpOption, cancellation);
        response.EnsureSuccessStatusCode();
        return response;
    }

    private async Task<string> GetStringAsync(string uri, CancellationToken cancellation)
    {
        var response = await GetAsync(uri, cancellation: cancellation);       
        return await response.Content.ReadAsStringAsync();
    }

    /// <inheritdoc/>
    public async Task<AvailableVersion?> GetLatestVersionAsync(CancellationToken cancellation)
    {
        var uri = "releases/latest";
        var json = await GetStringAsync(uri, cancellation);
        var version = JsonUtils.Deserialize<AvailableVersion>(json);
        return version;
    }

    /// <inheritdoc/>
    public async Task<ReleaseMetadata?> GetReleaseMetadataAsync(Version version, CancellationToken cancellation)
    {
        var uri = $"releases/tags/v{version}";
        var json = await GetStringAsync(uri, cancellation);
        var release = JsonUtils.Deserialize<ReleaseMetadata>(json);
        return release;
    }

    /// <inheritdoc/>
    public async Task DownloadAsync(Version version, string fileUri, string targetPath, Action<double>? progressCallback = null, CancellationToken cancellation = default)
    {
        var uri = $"releases/files/v{version}/{fileUri}";
        var response = await GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellation);
        var buffer = new byte[81920];
        var read = 0;
        var received = 0;
        using var fileStream = File.OpenWrite(targetPath);
        await using var responseStream = await response.Content.ReadAsStreamAsync();
        long total = response.Content.Headers.ContentLength ?? 1;
        do
        {
            read = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellation);
            if (read > 0)
            {
                await fileStream.WriteAsync(buffer, 0, read, cancellation);
                received += read;
                progressCallback?.Invoke(received * 100d / total);
            }
        } while (read > 0);
    }
}